package com.jsb.laundry.common.utils.id;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Instant;

/**
 * 该实现使用Snowflake ID生成器生成唯一ID，
 * 方法是将时间戳、区域ID、工作者ID和序列号组合成一个64位值。
 * nextId()方法通过获取当前时间(递增序列号)来生成一个新ID
 *
 * @author zmy
 * @create 2023-02-02 0:07
 */

public class OrderIdGenerator {

    private static final Logger LOGGER = LoggerFactory.getLogger(OrderIdGenerator.class);
    private static final long EPOCH = Instant.parse("2022-11-07T09:30:00Z").toEpochMilli();
    private static final int NUM_BITS_TIMESTAMP = 41;
    private static final int NUM_BITS_REGION = 5;
    private static final int NUM_BITS_WORKER = 5;
    private static final int NUM_BITS_SEQUENCE = 12;

    private static final int MAX_REGION = (int) (Math.pow(2, NUM_BITS_REGION) - 1);
    private static final int MAX_WORKER = (int) (Math.pow(2, NUM_BITS_WORKER) - 1);
    private static final int MAX_SEQUENCE = (int) (Math.pow(2, NUM_BITS_SEQUENCE) - 1);

    private int regionId;
    private int workerId;
    private long lastTimestamp = -1L;
    private int sequence = 0;

    public OrderIdGenerator(int regionId, int workerId) {
        if (regionId < 0 || regionId > MAX_REGION) {
            throw new IllegalArgumentException(String.format("Region ID must be between 0 and %d.", MAX_REGION));
        }
        if (workerId < 0 || workerId > MAX_WORKER) {
            throw new IllegalArgumentException(String.format("Worker ID must be between 0 and %d.", MAX_WORKER));
        }
        this.regionId = regionId;
        this.workerId = workerId;
    }

    private long tilNextMillis(long lastTimestamp) {
        long currentTimestamp = Instant.now().toEpochMilli() - EPOCH;
        while (currentTimestamp <= lastTimestamp) {
            currentTimestamp = Instant.now().toEpochMilli() - EPOCH;
        }
        return currentTimestamp;
    }

    /**
     * 获取编号
     *
     * @return
     */
    public synchronized long nextId() {
        long currentTimestamp = Instant.now().toEpochMilli() - EPOCH;
        if (currentTimestamp < lastTimestamp) {
            throw new IllegalStateException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds.", lastTimestamp - currentTimestamp));
        }
        if (currentTimestamp == lastTimestamp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0) {
                currentTimestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }
        lastTimestamp = currentTimestamp;
        return ((currentTimestamp & 0x1FFFFFFFFFFL) << 22) | (regionId << 17) | (workerId << 12) | sequence;
    }

}

